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 | |
18 | MisplacedWideningCastCheck::MisplacedWideningCastCheck( |
19 | StringRef Name, ClangTidyContext *Context) |
20 | : ClangTidyCheck(Name, Context), |
21 | CheckImplicitCasts(Options.get(LocalName: "CheckImplicitCasts" , Default: false)) {} |
22 | |
23 | void MisplacedWideningCastCheck::storeOptions( |
24 | ClangTidyOptions::OptionMap &Opts) { |
25 | Options.store(Options&: Opts, LocalName: "CheckImplicitCasts" , Value: CheckImplicitCasts); |
26 | } |
27 | |
28 | void MisplacedWideningCastCheck::registerMatchers(MatchFinder *Finder) { |
29 | const auto Calc = |
30 | expr(anyOf(binaryOperator(hasAnyOperatorName("+" , "-" , "*" , "<<" )), |
31 | unaryOperator(hasOperatorName(Name: "~" ))), |
32 | hasType(InnerMatcher: isInteger())) |
33 | .bind(ID: "Calc" ); |
34 | |
35 | const auto ExplicitCast = explicitCastExpr(hasDestinationType(InnerMatcher: isInteger()), |
36 | has(ignoringParenImpCasts(InnerMatcher: Calc))); |
37 | const auto ImplicitCast = |
38 | implicitCastExpr(hasImplicitDestinationType(InnerMatcher: isInteger()), |
39 | has(ignoringParenImpCasts(InnerMatcher: Calc))); |
40 | const auto Cast = |
41 | traverse(TK: TK_AsIs, InnerMatcher: expr(anyOf(ExplicitCast, ImplicitCast)).bind(ID: "Cast" )); |
42 | |
43 | Finder->addMatcher(NodeMatch: varDecl(hasInitializer(InnerMatcher: Cast)), Action: this); |
44 | Finder->addMatcher(NodeMatch: returnStmt(hasReturnValue(InnerMatcher: Cast)), Action: this); |
45 | Finder->addMatcher(NodeMatch: callExpr(hasAnyArgument(InnerMatcher: Cast)), Action: this); |
46 | Finder->addMatcher(NodeMatch: binaryOperator(hasOperatorName(Name: "=" ), hasRHS(InnerMatcher: Cast)), Action: this); |
47 | Finder->addMatcher( |
48 | NodeMatch: binaryOperator(isComparisonOperator(), hasEitherOperand(InnerMatcher: Cast)), Action: this); |
49 | } |
50 | |
51 | static unsigned getMaxCalculationWidth(const ASTContext &Context, |
52 | const Expr *E) { |
53 | E = E->IgnoreParenImpCasts(); |
54 | |
55 | if (const auto *Bop = dyn_cast<BinaryOperator>(Val: E)) { |
56 | unsigned LHSWidth = getMaxCalculationWidth(Context, E: Bop->getLHS()); |
57 | unsigned RHSWidth = getMaxCalculationWidth(Context, E: Bop->getRHS()); |
58 | if (Bop->getOpcode() == BO_Mul) |
59 | return LHSWidth + RHSWidth; |
60 | if (Bop->getOpcode() == BO_Add) |
61 | return std::max(a: LHSWidth, b: RHSWidth) + 1; |
62 | if (Bop->getOpcode() == BO_Rem) { |
63 | Expr::EvalResult Result; |
64 | if (Bop->getRHS()->EvaluateAsInt(Result, Ctx: Context)) |
65 | return Result.Val.getInt().getActiveBits(); |
66 | } else if (Bop->getOpcode() == BO_Shl) { |
67 | Expr::EvalResult Result; |
68 | if (Bop->getRHS()->EvaluateAsInt(Result, Ctx: 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>(Val: 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>(Val: E)) { |
86 | return I->getValue().getActiveBits(); |
87 | } |
88 | |
89 | return Context.getIntWidth(T: E->getType()); |
90 | } |
91 | |
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 | |
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 | |
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 | |
165 | static bool isFirstWider(BuiltinType::Kind First, BuiltinType::Kind Second) { |
166 | int FirstSize = 0, SecondSize = 0; |
167 | if ((FirstSize = relativeIntSizes(Kind: First)) != 0 && |
168 | (SecondSize = relativeIntSizes(Kind: Second)) != 0) |
169 | return FirstSize > SecondSize; |
170 | if ((FirstSize = relativeCharSizes(Kind: First)) != 0 && |
171 | (SecondSize = relativeCharSizes(Kind: Second)) != 0) |
172 | return FirstSize > SecondSize; |
173 | if ((FirstSize = relativeCharSizesW(Kind: First)) != 0 && |
174 | (SecondSize = relativeCharSizesW(Kind: Second)) != 0) |
175 | return FirstSize > SecondSize; |
176 | return false; |
177 | } |
178 | |
179 | void MisplacedWideningCastCheck::check(const MatchFinder::MatchResult &Result) { |
180 | const auto *Cast = Result.Nodes.getNodeAs<CastExpr>(ID: "Cast" ); |
181 | if (!CheckImplicitCasts && isa<ImplicitCastExpr>(Val: Cast)) |
182 | return; |
183 | if (Cast->getBeginLoc().isMacroID()) |
184 | return; |
185 | |
186 | const auto *Calc = Result.Nodes.getNodeAs<Expr>(ID: "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(T: CastType) < Context.getIntWidth(T: 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(T: CastType) == Context.getIntWidth(T: CalcType)) { |
207 | const auto *CastBuiltinType = |
208 | dyn_cast<BuiltinType>(Val: CastType->getUnqualifiedDesugaredType()); |
209 | const auto *CalcBuiltinType = |
210 | dyn_cast<BuiltinType>(Val: 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(T: CalcType) >= getMaxCalculationWidth(Context, E: 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 | |