clang-tools 19.0.0git
ImplicitWideningOfMultiplicationResultCheck.cpp
Go to the documentation of this file.
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
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 IncludeInserter(Options.getLocalOrGlobal("IncludeStyle",
46 utils::IncludeSorter::IS_LLVM),
47 areDiagsSelfContained()) {}
48
50 const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
51 IncludeInserter.registerPreprocessor(PP);
52}
53
56 Options.store(Opts, "UseCXXStaticCastsInCppSources",
57 UseCXXStaticCastsInCppSources);
58 Options.store(Opts, "UseCXXHeadersInCppSources", UseCXXHeadersInCppSources);
59 Options.store(Opts, "IncludeStyle", IncludeInserter.getStyle());
60}
61
62std::optional<FixItHint>
63ImplicitWideningOfMultiplicationResultCheck::includeStddefHeader(
64 SourceLocation File) {
65 return IncludeInserter.createIncludeInsertion(
66 Result->SourceManager->getFileID(File),
67 ShouldUseCXXHeader ? "<cstddef>" : "<stddef.h>");
68}
69
70void 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(ETy);
83 unsigned TgtWidth = Context->getIntWidth(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 E->getEndLoc(), 0, *Result->SourceManager, getLangOpts());
105 if (ShouldUseCXXStaticCast)
106 Diag << FixItHint::CreateInsertion(
107 E->getBeginLoc(), "static_cast<" + Ty.getAsString() + ">(")
108 << FixItHint::CreateInsertion(EndLoc, ")");
109 else
110 Diag << FixItHint::CreateInsertion(E->getBeginLoc(),
111 "(" + Ty.getAsString() + ")(")
112 << FixItHint::CreateInsertion(EndLoc, ")");
113 Diag << includeStddefHeader(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(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(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(LHS->getBeginLoc(),
140 "static_cast<" +
141 WideExprTy.getAsString() + ">(")
142 << FixItHint::CreateInsertion(
143 Lexer::getLocForEndOfToken(LHS->getEndLoc(), 0,
144 *Result->SourceManager,
145 getLangOpts()),
146 ")");
147 else
148 Diag << FixItHint::CreateInsertion(LHS->getBeginLoc(),
149 "(" + WideExprTy.getAsString() + ")");
150 Diag << includeStddefHeader(LHS->getBeginLoc());
151 }
152}
153
154void 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>(E)) {
162 PointerExpr = BO->getLHS();
163 IndexExpr = BO->getRHS();
164 } else if (const auto *ASE = dyn_cast<ArraySubscriptExpr>(E)) {
165 PointerExpr = ASE->getLHS();
166 IndexExpr = ASE->getRHS();
167 } else
168 return;
169
170 if (IndexExpr->getType()->isPointerType())
171 std::swap(PointerExpr, 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(IndexExprType) >= Context->getIntWidth(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(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 IndexExpr->getEndLoc(), 0, *Result->SourceManager, getLangOpts());
217 if (ShouldUseCXXStaticCast)
218 Diag << FixItHint::CreateInsertion(
219 IndexExpr->getBeginLoc(),
220 (Twine("static_cast<") + TyAsString + ">(").str())
221 << FixItHint::CreateInsertion(EndLoc, ")");
222 else
223 Diag << FixItHint::CreateInsertion(IndexExpr->getBeginLoc(),
224 (Twine("(") + TyAsString + ")(").str())
225 << FixItHint::CreateInsertion(EndLoc, ")");
226 Diag << includeStddefHeader(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 LHS->getBeginLoc(),
238 (Twine("static_cast<") + TyAsString + ">(").str())
239 << FixItHint::CreateInsertion(
240 Lexer::getLocForEndOfToken(IndexExpr->getEndLoc(), 0,
241 *Result->SourceManager,
242 getLangOpts()),
243 ")");
244 else
245 Diag << FixItHint::CreateInsertion(LHS->getBeginLoc(),
246 (Twine("(") + TyAsString + ")").str());
247 Diag << includeStddefHeader(LHS->getBeginLoc());
248 }
249}
250
252 MatchFinder *Finder) {
253 Finder->addMatcher(implicitCastExpr(unless(anyOf(containsErrors(),
254 isInTemplateInstantiation(),
255 isPartOfExplicitCast())),
256 hasCastKind(CK_IntegralCast))
257 .bind("x"),
258 this);
259 Finder->addMatcher(
260 arraySubscriptExpr(unless(isInTemplateInstantiation())).bind("x"), this);
261 Finder->addMatcher(binaryOperator(unless(isInTemplateInstantiation()),
262 hasType(isAnyPointer()),
263 hasAnyOperatorName("+", "-", "+=", "-="))
264 .bind("x"),
265 this);
266}
267
269 const MatchFinder::MatchResult &Result) {
270 this->Result = &Result;
271 ShouldUseCXXStaticCast =
272 UseCXXStaticCastsInCppSources && Result.Context->getLangOpts().CPlusPlus;
273 ShouldUseCXXHeader =
274 UseCXXHeadersInCppSources && Result.Context->getLangOpts().CPlusPlus;
275
276 if (const auto *MatchedDecl = Result.Nodes.getNodeAs<ImplicitCastExpr>("x"))
277 handleImplicitCastExpr(MatchedDecl);
278 else if (const auto *MatchedDecl =
279 Result.Nodes.getNodeAs<ArraySubscriptExpr>("x"))
280 handlePointerOffsetting(MatchedDecl);
281 else if (const auto *MatchedDecl =
282 Result.Nodes.getNodeAs<BinaryOperator>("x"))
283 handlePointerOffsetting(MatchedDecl);
284}
285
286} // namespace clang::tidy::bugprone
const Expr * E
llvm::SmallString< 256U > Name
::clang::DynTypedNode Node
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
Base class for all clang-tidy checks.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
const LangOptions & getLangOpts() const
Returns the language options from the context.
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) override
Override this to register PPCallbacks in the preprocessor.
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
void registerPreprocessor(Preprocessor *PP)
Registers this with the Preprocessor PP, must be called before this class is used.
std::optional< FixItHint > createIncludeInsertion(FileID FileID, llvm::StringRef Header)
Creates a Header inclusion directive fixit in the File FileID.
IncludeSorter::IncludeStyle getStyle() const
AST_MATCHER(clang::VarDecl, hasConstantDeclaration)
static const Expr * getLHSOfMulBinOp(const Expr *E)
llvm::StringMap< ClangTidyValue > OptionMap