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