clang-tools 20.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 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 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(ETy);
85 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 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 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 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(LHS->getBeginLoc(),
155 "static_cast<" +
156 WideExprTy.getAsString() + ">(")
157 << FixItHint::CreateInsertion(
158 Lexer::getLocForEndOfToken(LHS->getEndLoc(), 0,
159 *Result->SourceManager,
160 getLangOpts()),
161 ")");
162 else
163 Diag << FixItHint::CreateInsertion(LHS->getBeginLoc(),
164 "(" + WideExprTy.getAsString() + ")");
165 Diag << includeStddefHeader(LHS->getBeginLoc());
166 }
167}
168
169void 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>(E)) {
177 PointerExpr = BO->getLHS();
178 IndexExpr = BO->getRHS();
179 } else if (const auto *ASE = dyn_cast<ArraySubscriptExpr>(E)) {
180 PointerExpr = ASE->getLHS();
181 IndexExpr = ASE->getRHS();
182 } else
183 return;
184
185 if (IndexExpr->getType()->isPointerType())
186 std::swap(PointerExpr, 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(IndexExprType) >= Context->getIntWidth(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(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 IndexExpr->getEndLoc(), 0, *Result->SourceManager, getLangOpts());
232 if (ShouldUseCXXStaticCast)
233 Diag << FixItHint::CreateInsertion(
234 IndexExpr->getBeginLoc(),
235 (Twine("static_cast<") + TyAsString + ">(").str())
236 << FixItHint::CreateInsertion(EndLoc, ")");
237 else
238 Diag << FixItHint::CreateInsertion(IndexExpr->getBeginLoc(),
239 (Twine("(") + TyAsString + ")(").str())
240 << FixItHint::CreateInsertion(EndLoc, ")");
241 Diag << includeStddefHeader(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 LHS->getBeginLoc(),
253 (Twine("static_cast<") + TyAsString + ">(").str())
254 << FixItHint::CreateInsertion(
255 Lexer::getLocForEndOfToken(IndexExpr->getEndLoc(), 0,
256 *Result->SourceManager,
257 getLangOpts()),
258 ")");
259 else
260 Diag << FixItHint::CreateInsertion(LHS->getBeginLoc(),
261 (Twine("(") + TyAsString + ")").str());
262 Diag << includeStddefHeader(LHS->getBeginLoc());
263 }
264}
265
267 MatchFinder *Finder) {
268 Finder->addMatcher(implicitCastExpr(unless(anyOf(containsErrors(),
269 isInTemplateInstantiation(),
270 isPartOfExplicitCast())),
271 hasCastKind(CK_IntegralCast))
272 .bind("x"),
273 this);
274 Finder->addMatcher(
275 arraySubscriptExpr(unless(isInTemplateInstantiation())).bind("x"), this);
276 Finder->addMatcher(binaryOperator(unless(isInTemplateInstantiation()),
277 hasType(isAnyPointer()),
278 hasAnyOperatorName("+", "-", "+=", "-="))
279 .bind("x"),
280 this);
281}
282
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>("x"))
292 handleImplicitCastExpr(MatchedDecl);
293 else if (const auto *MatchedDecl =
294 Result.Nodes.getNodeAs<ArraySubscriptExpr>("x"))
295 handlePointerOffsetting(MatchedDecl);
296 else if (const auto *MatchedDecl =
297 Result.Nodes.getNodeAs<BinaryOperator>("x"))
298 handlePointerOffsetting(MatchedDecl);
299}
300
301} // 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