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