blob: 7919258e6508fe7d240323f8c57023a94f14a6fb [file] [log] [blame]
Daniel Marjamakiad329372016-02-09 14:08:49 +00001//===--- MisplacedWideningCastCheck.cpp - clang-tidy-----------------------===//
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 "MisplacedWideningCastCheck.h"
11#include "clang/AST/ASTContext.h"
12#include "clang/ASTMatchers/ASTMatchFinder.h"
Etienne Bergeron9d265992016-04-21 16:57:56 +000013#include "../utils/Matchers.h"
Daniel Marjamakiad329372016-02-09 14:08:49 +000014
15using namespace clang::ast_matchers;
16
17namespace clang {
18namespace tidy {
19namespace misc {
20
Gabor Horvath5273f612016-04-06 12:04:51 +000021MisplacedWideningCastCheck::MisplacedWideningCastCheck(
22 StringRef Name, ClangTidyContext *Context)
23 : ClangTidyCheck(Name, Context),
24 CheckImplicitCasts(Options.get("CheckImplicitCasts", true)) {}
25
26void MisplacedWideningCastCheck::storeOptions(
27 ClangTidyOptions::OptionMap &Opts) {
28 Options.store(Opts, "CheckImplicitCasts", CheckImplicitCasts);
29}
30
Daniel Marjamakiad329372016-02-09 14:08:49 +000031void MisplacedWideningCastCheck::registerMatchers(MatchFinder *Finder) {
Etienne Bergeron53f7c0e2016-04-07 14:52:52 +000032 const auto Calc =
33 expr(anyOf(binaryOperator(
34 anyOf(hasOperatorName("+"), hasOperatorName("-"),
35 hasOperatorName("*"), hasOperatorName("<<"))),
36 unaryOperator(hasOperatorName("~"))),
37 hasType(isInteger()))
38 .bind("Calc");
Daniel Marjamakiad329372016-02-09 14:08:49 +000039
Piotr Padlewskie93a73f2016-05-31 15:26:56 +000040 const auto ExplicitCast = explicitCastExpr(hasDestinationType(isInteger()),
41 has(ignoringParenImpCasts(Calc)));
Etienne Bergeron53f7c0e2016-04-07 14:52:52 +000042 const auto ImplicitCast =
Piotr Padlewskie93a73f2016-05-31 15:26:56 +000043 implicitCastExpr(hasImplicitDestinationType(isInteger()),
44 has(ignoringParenImpCasts(Calc)));
Etienne Bergeron53f7c0e2016-04-07 14:52:52 +000045 const auto Cast = expr(anyOf(ExplicitCast, ImplicitCast)).bind("Cast");
Daniel Marjamakiad329372016-02-09 14:08:49 +000046
Gabor Horvath5273f612016-04-06 12:04:51 +000047 Finder->addMatcher(varDecl(hasInitializer(Cast)), this);
48 Finder->addMatcher(returnStmt(hasReturnValue(Cast)), this);
49 Finder->addMatcher(callExpr(hasAnyArgument(Cast)), this);
Daniel Marjamakiad329372016-02-09 14:08:49 +000050 Finder->addMatcher(binaryOperator(hasOperatorName("="), hasRHS(Cast)), this);
Gabor Horvath5273f612016-04-06 12:04:51 +000051 Finder->addMatcher(
Etienne Bergeron9d265992016-04-21 16:57:56 +000052 binaryOperator(matchers::isComparisonOperator(),
Etienne Bergeron53f7c0e2016-04-07 14:52:52 +000053 hasEitherOperand(Cast)),
Gabor Horvath5273f612016-04-06 12:04:51 +000054 this);
Daniel Marjamakiad329372016-02-09 14:08:49 +000055}
56
Etienne Bergeron53f7c0e2016-04-07 14:52:52 +000057static unsigned getMaxCalculationWidth(const ASTContext &Context,
58 const Expr *E) {
Daniel Marjamakiad329372016-02-09 14:08:49 +000059 E = E->IgnoreParenImpCasts();
60
61 if (const auto *Bop = dyn_cast<BinaryOperator>(E)) {
62 unsigned LHSWidth = getMaxCalculationWidth(Context, Bop->getLHS());
63 unsigned RHSWidth = getMaxCalculationWidth(Context, Bop->getRHS());
64 if (Bop->getOpcode() == BO_Mul)
65 return LHSWidth + RHSWidth;
66 if (Bop->getOpcode() == BO_Add)
67 return std::max(LHSWidth, RHSWidth) + 1;
68 if (Bop->getOpcode() == BO_Rem) {
69 llvm::APSInt Val;
70 if (Bop->getRHS()->EvaluateAsInt(Val, Context))
71 return Val.getActiveBits();
72 } else if (Bop->getOpcode() == BO_Shl) {
73 llvm::APSInt Bits;
74 if (Bop->getRHS()->EvaluateAsInt(Bits, Context)) {
75 // We don't handle negative values and large values well. It is assumed
76 // that compiler warnings are written for such values so the user will
77 // fix that.
78 return LHSWidth + Bits.getExtValue();
79 }
80
81 // Unknown bitcount, assume there is truncation.
82 return 1024U;
83 }
84 } else if (const auto *Uop = dyn_cast<UnaryOperator>(E)) {
85 // There is truncation when ~ is used.
86 if (Uop->getOpcode() == UO_Not)
87 return 1024U;
88
89 QualType T = Uop->getType();
90 return T->isIntegerType() ? Context.getIntWidth(T) : 1024U;
91 } else if (const auto *I = dyn_cast<IntegerLiteral>(E)) {
92 return I->getValue().getActiveBits();
93 }
94
95 return Context.getIntWidth(E->getType());
96}
97
Etienne Bergeron53f7c0e2016-04-07 14:52:52 +000098static int relativeIntSizes(BuiltinType::Kind Kind) {
99 switch (Kind) {
100 case BuiltinType::UChar:
101 return 1;
102 case BuiltinType::SChar:
103 return 1;
104 case BuiltinType::Char_U:
105 return 1;
106 case BuiltinType::Char_S:
107 return 1;
108 case BuiltinType::UShort:
109 return 2;
110 case BuiltinType::Short:
111 return 2;
112 case BuiltinType::UInt:
113 return 3;
114 case BuiltinType::Int:
115 return 3;
116 case BuiltinType::ULong:
117 return 4;
118 case BuiltinType::Long:
119 return 4;
120 case BuiltinType::ULongLong:
121 return 5;
122 case BuiltinType::LongLong:
123 return 5;
124 case BuiltinType::UInt128:
125 return 6;
126 case BuiltinType::Int128:
127 return 6;
128 default:
129 return 0;
130 }
Gabor Horvath5273f612016-04-06 12:04:51 +0000131}
132
Etienne Bergeron53f7c0e2016-04-07 14:52:52 +0000133static int relativeCharSizes(BuiltinType::Kind Kind) {
134 switch (Kind) {
135 case BuiltinType::UChar:
136 return 1;
137 case BuiltinType::SChar:
138 return 1;
139 case BuiltinType::Char_U:
140 return 1;
141 case BuiltinType::Char_S:
142 return 1;
143 case BuiltinType::Char16:
144 return 2;
145 case BuiltinType::Char32:
146 return 3;
147 default:
148 return 0;
149 }
Gabor Horvath5273f612016-04-06 12:04:51 +0000150}
151
Etienne Bergeron53f7c0e2016-04-07 14:52:52 +0000152static int relativeCharSizesW(BuiltinType::Kind Kind) {
153 switch (Kind) {
154 case BuiltinType::UChar:
155 return 1;
156 case BuiltinType::SChar:
157 return 1;
158 case BuiltinType::Char_U:
159 return 1;
160 case BuiltinType::Char_S:
161 return 1;
162 case BuiltinType::WChar_U:
163 return 2;
164 case BuiltinType::WChar_S:
165 return 2;
166 default:
167 return 0;
168 }
Gabor Horvath5273f612016-04-06 12:04:51 +0000169}
170
171static bool isFirstWider(BuiltinType::Kind First, BuiltinType::Kind Second) {
Gabor Horvath5273f612016-04-06 12:04:51 +0000172 int FirstSize, SecondSize;
Etienne Bergeron53f7c0e2016-04-07 14:52:52 +0000173 if ((FirstSize = relativeIntSizes(First)) != 0 &&
174 (SecondSize = relativeIntSizes(Second)) != 0)
Gabor Horvath5273f612016-04-06 12:04:51 +0000175 return FirstSize > SecondSize;
Etienne Bergeron53f7c0e2016-04-07 14:52:52 +0000176 if ((FirstSize = relativeCharSizes(First)) != 0 &&
177 (SecondSize = relativeCharSizes(Second)) != 0)
Gabor Horvath5273f612016-04-06 12:04:51 +0000178 return FirstSize > SecondSize;
Etienne Bergeron53f7c0e2016-04-07 14:52:52 +0000179 if ((FirstSize = relativeCharSizesW(First)) != 0 &&
180 (SecondSize = relativeCharSizesW(Second)) != 0)
Gabor Horvath5273f612016-04-06 12:04:51 +0000181 return FirstSize > SecondSize;
182 return false;
183}
184
Daniel Marjamakiad329372016-02-09 14:08:49 +0000185void MisplacedWideningCastCheck::check(const MatchFinder::MatchResult &Result) {
Gabor Horvath5273f612016-04-06 12:04:51 +0000186 const auto *Cast = Result.Nodes.getNodeAs<CastExpr>("Cast");
187 if (!CheckImplicitCasts && isa<ImplicitCastExpr>(Cast))
188 return;
Daniel Marjamakiad329372016-02-09 14:08:49 +0000189 if (Cast->getLocStart().isMacroID())
190 return;
191
192 const auto *Calc = Result.Nodes.getNodeAs<Expr>("Calc");
193 if (Calc->getLocStart().isMacroID())
194 return;
195
196 ASTContext &Context = *Result.Context;
197
198 QualType CastType = Cast->getType();
199 QualType CalcType = Calc->getType();
200
Daniel Marjamaki79355772016-02-12 07:51:10 +0000201 // Explicit truncation using cast.
202 if (Context.getIntWidth(CastType) < Context.getIntWidth(CalcType))
Daniel Marjamakiad329372016-02-09 14:08:49 +0000203 return;
204
Daniel Marjamaki79355772016-02-12 07:51:10 +0000205 // If CalcType and CastType have same size then there is no real danger, but
206 // there can be a portability problem.
Gabor Horvath5273f612016-04-06 12:04:51 +0000207
Daniel Marjamaki79355772016-02-12 07:51:10 +0000208 if (Context.getIntWidth(CastType) == Context.getIntWidth(CalcType)) {
Gabor Horvath5273f612016-04-06 12:04:51 +0000209 const auto *CastBuiltinType =
210 dyn_cast<BuiltinType>(CastType->getUnqualifiedDesugaredType());
211 const auto *CalcBuiltinType =
212 dyn_cast<BuiltinType>(CalcType->getUnqualifiedDesugaredType());
213 if (CastBuiltinType && CalcBuiltinType &&
214 !isFirstWider(CastBuiltinType->getKind(), CalcBuiltinType->getKind()))
Daniel Marjamaki79355772016-02-12 07:51:10 +0000215 return;
Daniel Marjamaki79355772016-02-12 07:51:10 +0000216 }
217
218 // Don't write a warning if we can easily see that the result is not
219 // truncated.
Daniel Marjamakiad329372016-02-09 14:08:49 +0000220 if (Context.getIntWidth(CalcType) >= getMaxCalculationWidth(Context, Calc))
221 return;
222
223 diag(Cast->getLocStart(), "either cast from %0 to %1 is ineffective, or "
224 "there is loss of precision before the conversion")
225 << CalcType << CastType;
226}
227
228} // namespace misc
229} // namespace tidy
230} // namespace clang