1 | //===--- ImplicitWideningOfMultiplicationResultCheck.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 "ImplicitWideningOfMultiplicationResultCheck.h" |
10 | #include "clang/AST/ASTContext.h" |
11 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
12 | #include "clang/ASTMatchers/ASTMatchersMacros.h" |
13 | #include "clang/Lex/Lexer.h" |
14 | #include <optional> |
15 | |
16 | using namespace clang::ast_matchers; |
17 | |
18 | namespace clang::tidy::bugprone { |
19 | |
20 | namespace { |
21 | AST_MATCHER(ImplicitCastExpr, isPartOfExplicitCast) { |
22 | return Node.isPartOfExplicitCast(); |
23 | } |
24 | AST_MATCHER(Expr, containsErrors) { return Node.containsErrors(); } |
25 | } // namespace |
26 | |
27 | static const Expr *getLHSOfMulBinOp(const Expr *E) { |
28 | assert(E == E->IgnoreParens() && "Already skipped all parens!" ); |
29 | // Is this: long r = int(x) * int(y); ? |
30 | // FIXME: shall we skip brackets/casts/etc? |
31 | const auto *BO = dyn_cast<BinaryOperator>(Val: E); |
32 | if (!BO || BO->getOpcode() != BO_Mul) |
33 | // FIXME: what about: long r = int(x) + (int(y) * int(z)); ? |
34 | return nullptr; |
35 | return BO->getLHS()->IgnoreParens(); |
36 | } |
37 | |
38 | ImplicitWideningOfMultiplicationResultCheck:: |
39 | ImplicitWideningOfMultiplicationResultCheck(StringRef Name, |
40 | ClangTidyContext *Context) |
41 | : ClangTidyCheck(Name, Context), |
42 | UseCXXStaticCastsInCppSources( |
43 | Options.get(LocalName: "UseCXXStaticCastsInCppSources" , Default: true)), |
44 | UseCXXHeadersInCppSources(Options.get(LocalName: "UseCXXHeadersInCppSources" , Default: true)), |
45 | IgnoreConstantIntExpr(Options.get(LocalName: "IgnoreConstantIntExpr" , Default: false)), |
46 | IncludeInserter(Options.getLocalOrGlobal(LocalName: "IncludeStyle" , |
47 | Default: utils::IncludeSorter::IS_LLVM), |
48 | areDiagsSelfContained()) {} |
49 | |
50 | void ImplicitWideningOfMultiplicationResultCheck::registerPPCallbacks( |
51 | const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) { |
52 | IncludeInserter.registerPreprocessor(PP); |
53 | } |
54 | |
55 | void ImplicitWideningOfMultiplicationResultCheck::storeOptions( |
56 | ClangTidyOptions::OptionMap &Opts) { |
57 | Options.store(Options&: Opts, LocalName: "UseCXXStaticCastsInCppSources" , |
58 | Value: UseCXXStaticCastsInCppSources); |
59 | Options.store(Options&: Opts, LocalName: "UseCXXHeadersInCppSources" , Value: UseCXXHeadersInCppSources); |
60 | Options.store(Options&: Opts, LocalName: "IgnoreConstantIntExpr" , Value: IgnoreConstantIntExpr); |
61 | Options.store(Options&: Opts, LocalName: "IncludeStyle" , Value: IncludeInserter.getStyle()); |
62 | } |
63 | |
64 | std::optional<FixItHint> |
65 | ImplicitWideningOfMultiplicationResultCheck::( |
66 | SourceLocation File) { |
67 | return IncludeInserter.createIncludeInsertion( |
68 | FileID: Result->SourceManager->getFileID(SpellingLoc: File), |
69 | Header: ShouldUseCXXHeader ? "<cstddef>" : "<stddef.h>" ); |
70 | } |
71 | |
72 | void ImplicitWideningOfMultiplicationResultCheck::handleImplicitCastExpr( |
73 | const ImplicitCastExpr *ICE) { |
74 | ASTContext *Context = Result->Context; |
75 | |
76 | const Expr *E = ICE->getSubExpr()->IgnoreParens(); |
77 | QualType Ty = ICE->getType(); |
78 | QualType ETy = E->getType(); |
79 | |
80 | assert(!ETy->isDependentType() && !Ty->isDependentType() && |
81 | "Don't expect to ever get here in template Context." ); |
82 | |
83 | // This must be a widening cast. Else we do not care. |
84 | unsigned SrcWidth = Context->getIntWidth(T: ETy); |
85 | unsigned TgtWidth = Context->getIntWidth(T: Ty); |
86 | if (TgtWidth <= SrcWidth) |
87 | return; |
88 | |
89 | // Is the expression a compile-time constexpr that we know can fit in the |
90 | // source type? |
91 | if (IgnoreConstantIntExpr && ETy->isIntegerType() && |
92 | !ETy->isUnsignedIntegerType()) { |
93 | if (const auto ConstExprResult = E->getIntegerConstantExpr(*Context)) { |
94 | const auto TypeSize = Context->getTypeSize(T: ETy); |
95 | llvm::APSInt WidenedResult = ConstExprResult->extOrTrunc(TypeSize); |
96 | if (WidenedResult <= llvm::APSInt::getMaxValue(numBits: TypeSize, Unsigned: false) && |
97 | WidenedResult >= llvm::APSInt::getMinValue(numBits: TypeSize, Unsigned: false)) |
98 | return; |
99 | } |
100 | } |
101 | |
102 | // Does the index expression look like it might be unintentionally computed |
103 | // in a narrower-than-wanted type? |
104 | const Expr *LHS = getLHSOfMulBinOp(E); |
105 | if (!LHS) |
106 | return; |
107 | |
108 | // Ok, looks like we should diagnose this. |
109 | diag(E->getBeginLoc(), "performing an implicit widening conversion to type " |
110 | "%0 of a multiplication performed in type %1" ) |
111 | << Ty << E->getType(); |
112 | |
113 | { |
114 | auto Diag = diag(E->getBeginLoc(), |
115 | "make conversion explicit to silence this warning" , |
116 | DiagnosticIDs::Note) |
117 | << E->getSourceRange(); |
118 | const SourceLocation EndLoc = Lexer::getLocForEndOfToken( |
119 | Loc: E->getEndLoc(), Offset: 0, SM: *Result->SourceManager, LangOpts: getLangOpts()); |
120 | if (ShouldUseCXXStaticCast) |
121 | Diag << FixItHint::CreateInsertion( |
122 | InsertionLoc: E->getBeginLoc(), Code: "static_cast<" + Ty.getAsString() + ">(" ) |
123 | << FixItHint::CreateInsertion(InsertionLoc: EndLoc, Code: ")" ); |
124 | else |
125 | Diag << FixItHint::CreateInsertion(InsertionLoc: E->getBeginLoc(), |
126 | Code: "(" + Ty.getAsString() + ")(" ) |
127 | << FixItHint::CreateInsertion(InsertionLoc: EndLoc, Code: ")" ); |
128 | Diag << includeStddefHeader(File: E->getBeginLoc()); |
129 | } |
130 | |
131 | QualType WideExprTy; |
132 | // Get Ty of the same signedness as ExprTy, because we only want to suggest |
133 | // to widen the computation, but not change it's signedness domain. |
134 | if (Ty->isSignedIntegerType() == ETy->isSignedIntegerType()) |
135 | WideExprTy = Ty; |
136 | else if (Ty->isSignedIntegerType()) { |
137 | assert(ETy->isUnsignedIntegerType() && |
138 | "Expected source type to be signed." ); |
139 | WideExprTy = Context->getCorrespondingUnsignedType(T: Ty); |
140 | } else { |
141 | assert(Ty->isUnsignedIntegerType() && |
142 | "Expected target type to be unsigned." ); |
143 | assert(ETy->isSignedIntegerType() && |
144 | "Expected source type to be unsigned." ); |
145 | WideExprTy = Context->getCorrespondingSignedType(T: Ty); |
146 | } |
147 | |
148 | { |
149 | auto Diag = diag(E->getBeginLoc(), "perform multiplication in a wider type" , |
150 | DiagnosticIDs::Note) |
151 | << LHS->getSourceRange(); |
152 | |
153 | if (ShouldUseCXXStaticCast) |
154 | Diag << FixItHint::CreateInsertion(InsertionLoc: LHS->getBeginLoc(), |
155 | Code: "static_cast<" + |
156 | WideExprTy.getAsString() + ">(" ) |
157 | << FixItHint::CreateInsertion( |
158 | InsertionLoc: Lexer::getLocForEndOfToken(Loc: LHS->getEndLoc(), Offset: 0, |
159 | SM: *Result->SourceManager, |
160 | LangOpts: getLangOpts()), |
161 | Code: ")" ); |
162 | else |
163 | Diag << FixItHint::CreateInsertion(InsertionLoc: LHS->getBeginLoc(), |
164 | Code: "(" + WideExprTy.getAsString() + ")" ); |
165 | Diag << includeStddefHeader(File: LHS->getBeginLoc()); |
166 | } |
167 | } |
168 | |
169 | void ImplicitWideningOfMultiplicationResultCheck::handlePointerOffsetting( |
170 | const Expr *E) { |
171 | ASTContext *Context = Result->Context; |
172 | |
173 | // We are looking for a pointer offset operation, |
174 | // with one hand being a pointer, and another one being an offset. |
175 | const Expr *PointerExpr = nullptr, *IndexExpr = nullptr; |
176 | if (const auto *BO = dyn_cast<BinaryOperator>(Val: E)) { |
177 | PointerExpr = BO->getLHS(); |
178 | IndexExpr = BO->getRHS(); |
179 | } else if (const auto *ASE = dyn_cast<ArraySubscriptExpr>(Val: E)) { |
180 | PointerExpr = ASE->getLHS(); |
181 | IndexExpr = ASE->getRHS(); |
182 | } else |
183 | return; |
184 | |
185 | if (IndexExpr->getType()->isPointerType()) |
186 | std::swap(a&: PointerExpr, b&: IndexExpr); |
187 | |
188 | if (!PointerExpr->getType()->isPointerType() || |
189 | IndexExpr->getType()->isPointerType()) |
190 | return; |
191 | |
192 | IndexExpr = IndexExpr->IgnoreParens(); |
193 | |
194 | QualType IndexExprType = IndexExpr->getType(); |
195 | |
196 | // If the index expression's type is not known (i.e. we are in a template), |
197 | // we can't do anything here. |
198 | if (IndexExprType->isDependentType()) |
199 | return; |
200 | |
201 | QualType SSizeTy = Context->getPointerDiffType(); |
202 | QualType USizeTy = Context->getSizeType(); |
203 | QualType SizeTy = IndexExprType->isSignedIntegerType() ? SSizeTy : USizeTy; |
204 | // FIXME: is there a way to actually get the QualType for size_t/ptrdiff_t? |
205 | // Note that SizeTy.getAsString() will be unsigned long/..., NOT size_t! |
206 | StringRef TyAsString = |
207 | IndexExprType->isSignedIntegerType() ? "ptrdiff_t" : "size_t" ; |
208 | |
209 | // So, is size_t actually wider than the result of the multiplication? |
210 | if (Context->getIntWidth(T: IndexExprType) >= Context->getIntWidth(T: SizeTy)) |
211 | return; |
212 | |
213 | // Does the index expression look like it might be unintentionally computed |
214 | // in a narrower-than-wanted type? |
215 | const Expr *LHS = getLHSOfMulBinOp(E: IndexExpr); |
216 | if (!LHS) |
217 | return; |
218 | |
219 | // Ok, looks like we should diagnose this. |
220 | diag(E->getBeginLoc(), |
221 | "result of multiplication in type %0 is used as a pointer offset after " |
222 | "an implicit widening conversion to type '%1'" ) |
223 | << IndexExprType << TyAsString; |
224 | |
225 | { |
226 | auto Diag = diag(IndexExpr->getBeginLoc(), |
227 | "make conversion explicit to silence this warning" , |
228 | DiagnosticIDs::Note) |
229 | << IndexExpr->getSourceRange(); |
230 | const SourceLocation EndLoc = Lexer::getLocForEndOfToken( |
231 | Loc: IndexExpr->getEndLoc(), Offset: 0, SM: *Result->SourceManager, LangOpts: getLangOpts()); |
232 | if (ShouldUseCXXStaticCast) |
233 | Diag << FixItHint::CreateInsertion( |
234 | InsertionLoc: IndexExpr->getBeginLoc(), |
235 | Code: (Twine("static_cast<" ) + TyAsString + ">(" ).str()) |
236 | << FixItHint::CreateInsertion(InsertionLoc: EndLoc, Code: ")" ); |
237 | else |
238 | Diag << FixItHint::CreateInsertion(InsertionLoc: IndexExpr->getBeginLoc(), |
239 | Code: (Twine("(" ) + TyAsString + ")(" ).str()) |
240 | << FixItHint::CreateInsertion(InsertionLoc: EndLoc, Code: ")" ); |
241 | Diag << includeStddefHeader(File: IndexExpr->getBeginLoc()); |
242 | } |
243 | |
244 | { |
245 | auto Diag = |
246 | diag(IndexExpr->getBeginLoc(), "perform multiplication in a wider type" , |
247 | DiagnosticIDs::Note) |
248 | << LHS->getSourceRange(); |
249 | |
250 | if (ShouldUseCXXStaticCast) |
251 | Diag << FixItHint::CreateInsertion( |
252 | InsertionLoc: LHS->getBeginLoc(), |
253 | Code: (Twine("static_cast<" ) + TyAsString + ">(" ).str()) |
254 | << FixItHint::CreateInsertion( |
255 | InsertionLoc: Lexer::getLocForEndOfToken(Loc: IndexExpr->getEndLoc(), Offset: 0, |
256 | SM: *Result->SourceManager, |
257 | LangOpts: getLangOpts()), |
258 | Code: ")" ); |
259 | else |
260 | Diag << FixItHint::CreateInsertion(InsertionLoc: LHS->getBeginLoc(), |
261 | Code: (Twine("(" ) + TyAsString + ")" ).str()); |
262 | Diag << includeStddefHeader(File: LHS->getBeginLoc()); |
263 | } |
264 | } |
265 | |
266 | void ImplicitWideningOfMultiplicationResultCheck::registerMatchers( |
267 | MatchFinder *Finder) { |
268 | Finder->addMatcher(NodeMatch: implicitCastExpr(unless(anyOf(containsErrors(), |
269 | isInTemplateInstantiation(), |
270 | isPartOfExplicitCast())), |
271 | hasCastKind(Kind: CK_IntegralCast)) |
272 | .bind(ID: "x" ), |
273 | Action: this); |
274 | Finder->addMatcher( |
275 | NodeMatch: arraySubscriptExpr(unless(isInTemplateInstantiation())).bind(ID: "x" ), Action: this); |
276 | Finder->addMatcher(NodeMatch: binaryOperator(unless(isInTemplateInstantiation()), |
277 | hasType(InnerMatcher: isAnyPointer()), |
278 | hasAnyOperatorName("+" , "-" , "+=" , "-=" )) |
279 | .bind(ID: "x" ), |
280 | Action: this); |
281 | } |
282 | |
283 | void ImplicitWideningOfMultiplicationResultCheck::check( |
284 | const MatchFinder::MatchResult &Result) { |
285 | this->Result = &Result; |
286 | ShouldUseCXXStaticCast = |
287 | UseCXXStaticCastsInCppSources && Result.Context->getLangOpts().CPlusPlus; |
288 | ShouldUseCXXHeader = |
289 | UseCXXHeadersInCppSources && Result.Context->getLangOpts().CPlusPlus; |
290 | |
291 | if (const auto *MatchedDecl = Result.Nodes.getNodeAs<ImplicitCastExpr>(ID: "x" )) |
292 | handleImplicitCastExpr(ICE: MatchedDecl); |
293 | else if (const auto *MatchedDecl = |
294 | Result.Nodes.getNodeAs<ArraySubscriptExpr>(ID: "x" )) |
295 | handlePointerOffsetting(MatchedDecl); |
296 | else if (const auto *MatchedDecl = |
297 | Result.Nodes.getNodeAs<BinaryOperator>(ID: "x" )) |
298 | handlePointerOffsetting(MatchedDecl); |
299 | } |
300 | |
301 | } // namespace clang::tidy::bugprone |
302 | |