• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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