clang-tools 24.0.0git
ImplicitWideningOfMultiplicationResultCheck.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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
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
16using namespace clang::ast_matchers;
17
18namespace clang::tidy::bugprone {
19
20namespace {
21AST_MATCHER(ImplicitCastExpr, isPartOfExplicitCast) {
22 return Node.isPartOfExplicitCast();
23}
24AST_MATCHER(Expr, containsErrors) { return Node.containsErrors(); }
25} // namespace
26
27static 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>(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
40 ClangTidyContext *Context)
41 : ClangTidyCheck(Name, Context),
42 UseCXXStaticCastsInCppSources(
43 Options.get("UseCXXStaticCastsInCppSources", true)),
44 UseCXXHeadersInCppSources(Options.get("UseCXXHeadersInCppSources", true)),
45 IgnoreConstantIntExpr(Options.get("IgnoreConstantIntExpr", false)),
46 IncludeInserter(Options.getLocalOrGlobal("IncludeStyle",
47 utils::IncludeSorter::IS_LLVM),
48 areDiagsSelfContained()) {}
49
51 const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
52 IncludeInserter.registerPreprocessor(PP);
53}
54
57 Options.store(Opts, "UseCXXStaticCastsInCppSources",
58 UseCXXStaticCastsInCppSources);
59 Options.store(Opts, "UseCXXHeadersInCppSources", UseCXXHeadersInCppSources);
60 Options.store(Opts, "IgnoreConstantIntExpr", IgnoreConstantIntExpr);
61 Options.store(Opts, "IncludeStyle", IncludeInserter.getStyle());
62}
63
64std::optional<FixItHint>
65ImplicitWideningOfMultiplicationResultCheck::includeStddefHeader(
66 SourceLocation File) {
67 return IncludeInserter.createIncludeInsertion(
68 Result->SourceManager->getFileID(File),
69 ShouldUseCXXHeader ? "<cstddef>" : "<stddef.h>");
70}
71
72void ImplicitWideningOfMultiplicationResultCheck::handleImplicitCastExpr(
73 const ImplicitCastExpr *ICE) {
74 const ASTContext *Context = Result->Context;
75
76 const Expr *E = ICE->getSubExpr()->IgnoreParens();
77 const QualType Ty = ICE->getType();
78 const 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 const unsigned SrcWidth = Context->getIntWidth(ETy);
85 const unsigned TgtWidth = Context->getIntWidth(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(ETy);
95 const llvm::APSInt WidenedResult = ConstExprResult->extOrTrunc(TypeSize);
96 if (WidenedResult <= llvm::APSInt::getMaxValue(TypeSize, false) &&
97 WidenedResult >= llvm::APSInt::getMinValue(TypeSize, 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 const 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 E->getEndLoc(), 0, *Result->SourceManager, getLangOpts());
120 if (ShouldUseCXXStaticCast)
121 Diag << FixItHint::CreateInsertion(
122 E->getBeginLoc(), "static_cast<" + Ty.getAsString() + ">(")
123 << FixItHint::CreateInsertion(EndLoc, ")");
124 else
125 Diag << FixItHint::CreateInsertion(E->getBeginLoc(),
126 "(" + Ty.getAsString() + ")(")
127 << FixItHint::CreateInsertion(EndLoc, ")");
128 Diag << includeStddefHeader(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(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(Ty);
146 }
147
148 {
149 const auto Diag =
150 diag(E->getBeginLoc(), "perform multiplication in a wider type",
151 DiagnosticIDs::Note)
152 << LHS->getSourceRange();
153
154 if (ShouldUseCXXStaticCast)
155 Diag << FixItHint::CreateInsertion(LHS->getBeginLoc(),
156 "static_cast<" +
157 WideExprTy.getAsString() + ">(")
158 << FixItHint::CreateInsertion(
159 Lexer::getLocForEndOfToken(LHS->getEndLoc(), 0,
160 *Result->SourceManager,
161 getLangOpts()),
162 ")");
163 else
164 Diag << FixItHint::CreateInsertion(LHS->getBeginLoc(),
165 "(" + WideExprTy.getAsString() + ")");
166 Diag << includeStddefHeader(LHS->getBeginLoc());
167 }
168}
169
170void ImplicitWideningOfMultiplicationResultCheck::handlePointerOffsetting(
171 const Expr *E) {
172 const ASTContext *Context = Result->Context;
173
174 // We are looking for a pointer offset operation,
175 // with one hand being a pointer, and another one being an offset.
176 const Expr *PointerExpr = nullptr, *IndexExpr = nullptr;
177 if (const auto *BO = dyn_cast<BinaryOperator>(E)) {
178 PointerExpr = BO->getLHS();
179 IndexExpr = BO->getRHS();
180 } else if (const auto *ASE = dyn_cast<ArraySubscriptExpr>(E)) {
181 PointerExpr = ASE->getLHS();
182 IndexExpr = ASE->getRHS();
183 } else {
184 return;
185 }
186
187 if (IndexExpr->getType()->isPointerType())
188 std::swap(PointerExpr, IndexExpr);
189
190 if (!PointerExpr->getType()->isPointerType() ||
191 IndexExpr->getType()->isPointerType())
192 return;
193
194 IndexExpr = IndexExpr->IgnoreParens();
195
196 const QualType IndexExprType = IndexExpr->getType();
197
198 // If the index expression's type is not known (i.e. we are in a template),
199 // we can't do anything here.
200 if (IndexExprType->isDependentType())
201 return;
202
203 const QualType SSizeTy = Context->getPointerDiffType();
204 const QualType USizeTy = Context->getSizeType();
205 const QualType SizeTy =
206 IndexExprType->isSignedIntegerType() ? SSizeTy : USizeTy;
207 // FIXME: is there a way to actually get the QualType for size_t/ptrdiff_t?
208 // Note that SizeTy.getAsString() will be unsigned long/..., NOT size_t!
209 const StringRef TyAsString =
210 IndexExprType->isSignedIntegerType() ? "ptrdiff_t" : "size_t";
211
212 // So, is size_t actually wider than the result of the multiplication?
213 if (Context->getIntWidth(IndexExprType) >= Context->getIntWidth(SizeTy))
214 return;
215
216 // Does the index expression look like it might be unintentionally computed
217 // in a narrower-than-wanted type?
218 const Expr *LHS = getLHSOfMulBinOp(IndexExpr);
219 if (!LHS)
220 return;
221
222 // Ok, looks like we should diagnose this.
223 diag(E->getBeginLoc(),
224 "result of multiplication in type %0 is used as a pointer offset after "
225 "an implicit widening conversion to type '%1'")
226 << IndexExprType << TyAsString;
227
228 {
229 const auto Diag = diag(IndexExpr->getBeginLoc(),
230 "make conversion explicit to silence this warning",
231 DiagnosticIDs::Note)
232 << IndexExpr->getSourceRange();
233 const SourceLocation EndLoc = Lexer::getLocForEndOfToken(
234 IndexExpr->getEndLoc(), 0, *Result->SourceManager, getLangOpts());
235 if (ShouldUseCXXStaticCast)
236 Diag << FixItHint::CreateInsertion(
237 IndexExpr->getBeginLoc(),
238 (Twine("static_cast<") + TyAsString + ">(").str())
239 << FixItHint::CreateInsertion(EndLoc, ")");
240 else
241 Diag << FixItHint::CreateInsertion(IndexExpr->getBeginLoc(),
242 (Twine("(") + TyAsString + ")(").str())
243 << FixItHint::CreateInsertion(EndLoc, ")");
244 Diag << includeStddefHeader(IndexExpr->getBeginLoc());
245 }
246
247 {
248 const auto Diag =
249 diag(IndexExpr->getBeginLoc(), "perform multiplication in a wider type",
250 DiagnosticIDs::Note)
251 << LHS->getSourceRange();
252
253 if (ShouldUseCXXStaticCast)
254 Diag << FixItHint::CreateInsertion(
255 LHS->getBeginLoc(),
256 (Twine("static_cast<") + TyAsString + ">(").str())
257 << FixItHint::CreateInsertion(
258 Lexer::getLocForEndOfToken(IndexExpr->getEndLoc(), 0,
259 *Result->SourceManager,
260 getLangOpts()),
261 ")");
262 else
263 Diag << FixItHint::CreateInsertion(LHS->getBeginLoc(),
264 (Twine("(") + TyAsString + ")").str());
265 Diag << includeStddefHeader(LHS->getBeginLoc());
266 }
267}
268
270 MatchFinder *Finder) {
271 Finder->addMatcher(implicitCastExpr(unless(anyOf(containsErrors(),
272 isInTemplateInstantiation(),
273 isPartOfExplicitCast())),
274 hasCastKind(CK_IntegralCast))
275 .bind("x"),
276 this);
277 Finder->addMatcher(
278 arraySubscriptExpr(unless(isInTemplateInstantiation())).bind("x"), this);
279 Finder->addMatcher(binaryOperator(unless(isInTemplateInstantiation()),
280 hasType(isAnyPointer()),
281 hasAnyOperatorName("+", "-", "+=", "-="))
282 .bind("x"),
283 this);
284}
285
287 const MatchFinder::MatchResult &Result) {
288 this->Result = &Result;
289 ShouldUseCXXStaticCast =
290 UseCXXStaticCastsInCppSources && Result.Context->getLangOpts().CPlusPlus;
291 ShouldUseCXXHeader =
292 UseCXXHeadersInCppSources && Result.Context->getLangOpts().CPlusPlus;
293
294 if (const auto *MatchedDecl = Result.Nodes.getNodeAs<ImplicitCastExpr>("x"))
295 handleImplicitCastExpr(MatchedDecl);
296 else if (const auto *MatchedDecl =
297 Result.Nodes.getNodeAs<ArraySubscriptExpr>("x"))
298 handlePointerOffsetting(MatchedDecl);
299 else if (const auto *MatchedDecl =
300 Result.Nodes.getNodeAs<BinaryOperator>("x"))
301 handlePointerOffsetting(MatchedDecl);
302}
303
304} // namespace clang::tidy::bugprone
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) override
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
static const Expr * getLHSOfMulBinOp(const Expr *E)
AST_MATCHER(BinaryOperator, isRelationalOperator)
llvm::StringMap< ClangTidyValue > OptionMap