Skip to content

Commit a037cca

Browse files
author
z1_cciauto
authored
merge main into amd-staging (llvm#3716)
2 parents d7302b5 + 1fdb1e1 commit a037cca

File tree

109 files changed

+3694
-302
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

109 files changed

+3694
-302
lines changed

clang-tools-extra/clang-tidy/cppcoreguidelines/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ add_clang_library(clangTidyCppCoreGuidelinesModule STATIC
2121
OwningMemoryCheck.cpp
2222
PreferMemberInitializerCheck.cpp
2323
ProBoundsArrayToPointerDecayCheck.cpp
24+
ProBoundsAvoidUncheckedContainerAccess.cpp
2425
ProBoundsConstantArrayIndexCheck.cpp
2526
ProBoundsPointerArithmeticCheck.cpp
2627
ProTypeConstCastCheck.cpp

clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include "OwningMemoryCheck.h"
3737
#include "PreferMemberInitializerCheck.h"
3838
#include "ProBoundsArrayToPointerDecayCheck.h"
39+
#include "ProBoundsAvoidUncheckedContainerAccess.h"
3940
#include "ProBoundsConstantArrayIndexCheck.h"
4041
#include "ProBoundsPointerArithmeticCheck.h"
4142
#include "ProTypeConstCastCheck.h"
@@ -107,6 +108,8 @@ class CppCoreGuidelinesModule : public ClangTidyModule {
107108
"cppcoreguidelines-prefer-member-initializer");
108109
CheckFactories.registerCheck<ProBoundsArrayToPointerDecayCheck>(
109110
"cppcoreguidelines-pro-bounds-array-to-pointer-decay");
111+
CheckFactories.registerCheck<ProBoundsAvoidUncheckedContainerAccess>(
112+
"cppcoreguidelines-pro-bounds-avoid-unchecked-container-access");
110113
CheckFactories.registerCheck<ProBoundsConstantArrayIndexCheck>(
111114
"cppcoreguidelines-pro-bounds-constant-array-index");
112115
CheckFactories.registerCheck<ProBoundsPointerArithmeticCheck>(
Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
//===--- ProBoundsAvoidUncheckedContainerAccess.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 "ProBoundsAvoidUncheckedContainerAccess.h"
10+
#include "../utils/Matchers.h"
11+
#include "../utils/OptionsUtils.h"
12+
#include "clang/ASTMatchers/ASTMatchFinder.h"
13+
#include "llvm/ADT/StringRef.h"
14+
15+
using namespace clang::ast_matchers;
16+
17+
namespace clang::tidy::cppcoreguidelines {
18+
19+
static constexpr llvm::StringRef DefaultExclusionStr =
20+
"::std::map;::std::unordered_map;::std::flat_map";
21+
22+
ProBoundsAvoidUncheckedContainerAccess::ProBoundsAvoidUncheckedContainerAccess(
23+
StringRef Name, ClangTidyContext *Context)
24+
: ClangTidyCheck(Name, Context),
25+
ExcludedClasses(utils::options::parseStringList(
26+
Options.get("ExcludeClasses", DefaultExclusionStr))),
27+
FixMode(Options.get("FixMode", None)),
28+
FixFunction(Options.get("FixFunction", "gsl::at")),
29+
FixFunctionEmptyArgs(Options.get("FixFunctionEmptyArgs", FixFunction)) {}
30+
31+
void ProBoundsAvoidUncheckedContainerAccess::storeOptions(
32+
ClangTidyOptions::OptionMap &Opts) {
33+
Options.store(Opts, "ExcludeClasses",
34+
utils::options::serializeStringList(ExcludedClasses));
35+
Options.store(Opts, "FixMode", FixMode);
36+
Options.store(Opts, "FixFunction", FixFunction);
37+
Options.store(Opts, "FixFunctionEmptyArgs", FixFunctionEmptyArgs);
38+
}
39+
40+
// TODO: if at() is defined in another class in the class hierarchy of the class
41+
// that defines the operator[] we matched on, findAlternative() will not detect
42+
// it.
43+
static const CXXMethodDecl *
44+
findAlternativeAt(const CXXMethodDecl *MatchedOperator) {
45+
const CXXRecordDecl *Parent = MatchedOperator->getParent();
46+
const QualType SubscriptThisObjType =
47+
MatchedOperator->getFunctionObjectParameterReferenceType();
48+
49+
for (const CXXMethodDecl *Method : Parent->methods()) {
50+
// Require 'Method' to be as accessible as 'MatchedOperator' or more
51+
if (MatchedOperator->getAccess() < Method->getAccess())
52+
continue;
53+
54+
if (MatchedOperator->isConst() != Method->isConst())
55+
continue;
56+
57+
const QualType AtThisObjType =
58+
Method->getFunctionObjectParameterReferenceType();
59+
if (SubscriptThisObjType != AtThisObjType)
60+
continue;
61+
62+
if (!Method->getNameInfo().getName().isIdentifier() ||
63+
Method->getName() != "at")
64+
continue;
65+
66+
const bool SameReturnType =
67+
Method->getReturnType() == MatchedOperator->getReturnType();
68+
if (!SameReturnType)
69+
continue;
70+
71+
const bool SameNumberOfArguments =
72+
Method->getNumParams() == MatchedOperator->getNumParams();
73+
if (!SameNumberOfArguments)
74+
continue;
75+
76+
for (unsigned ArgInd = 0; ArgInd < Method->getNumParams(); ArgInd++) {
77+
const bool SameArgType =
78+
Method->parameters()[ArgInd]->getOriginalType() ==
79+
MatchedOperator->parameters()[ArgInd]->getOriginalType();
80+
if (!SameArgType)
81+
continue;
82+
}
83+
84+
return Method;
85+
}
86+
return nullptr;
87+
}
88+
89+
void ProBoundsAvoidUncheckedContainerAccess::registerMatchers(
90+
MatchFinder *Finder) {
91+
Finder->addMatcher(
92+
mapAnyOf(cxxOperatorCallExpr, cxxMemberCallExpr)
93+
.with(callee(
94+
cxxMethodDecl(
95+
hasOverloadedOperatorName("[]"),
96+
anyOf(parameterCountIs(0), parameterCountIs(1)),
97+
unless(matchers::matchesAnyListedName(ExcludedClasses)))
98+
.bind("operator")))
99+
.bind("caller"),
100+
this);
101+
}
102+
103+
void ProBoundsAvoidUncheckedContainerAccess::check(
104+
const MatchFinder::MatchResult &Result) {
105+
106+
const auto *MatchedExpr = Result.Nodes.getNodeAs<CallExpr>("caller");
107+
108+
if (FixMode == None) {
109+
diag(MatchedExpr->getCallee()->getBeginLoc(),
110+
"possibly unsafe 'operator[]', consider bounds-safe alternatives")
111+
<< MatchedExpr->getCallee()->getSourceRange();
112+
return;
113+
}
114+
115+
if (const auto *OCE = dyn_cast<CXXOperatorCallExpr>(MatchedExpr)) {
116+
// Case: a[i]
117+
const auto LeftBracket = SourceRange(OCE->getCallee()->getBeginLoc(),
118+
OCE->getCallee()->getBeginLoc());
119+
const auto RightBracket =
120+
SourceRange(OCE->getOperatorLoc(), OCE->getOperatorLoc());
121+
122+
if (FixMode == At) {
123+
// Case: a[i] => a.at(i)
124+
const auto *MatchedOperator =
125+
Result.Nodes.getNodeAs<CXXMethodDecl>("operator");
126+
const CXXMethodDecl *Alternative = findAlternativeAt(MatchedOperator);
127+
128+
if (!Alternative) {
129+
diag(MatchedExpr->getCallee()->getBeginLoc(),
130+
"possibly unsafe 'operator[]', consider "
131+
"bounds-safe alternatives")
132+
<< MatchedExpr->getCallee()->getSourceRange();
133+
return;
134+
}
135+
136+
diag(MatchedExpr->getCallee()->getBeginLoc(),
137+
"possibly unsafe 'operator[]', consider "
138+
"bounds-safe alternative 'at()'")
139+
<< MatchedExpr->getCallee()->getSourceRange()
140+
<< FixItHint::CreateReplacement(LeftBracket, ".at(")
141+
<< FixItHint::CreateReplacement(RightBracket, ")");
142+
143+
diag(Alternative->getBeginLoc(), "viable 'at()' is defined here",
144+
DiagnosticIDs::Note)
145+
<< Alternative->getNameInfo().getSourceRange();
146+
147+
} else if (FixMode == Function) {
148+
// Case: a[i] => f(a, i)
149+
//
150+
// Since C++23, the subscript operator may also be called without an
151+
// argument, which makes the following distinction necessary
152+
const bool EmptySubscript =
153+
MatchedExpr->getDirectCallee()->getNumParams() == 0;
154+
155+
if (EmptySubscript) {
156+
auto D = diag(MatchedExpr->getCallee()->getBeginLoc(),
157+
"possibly unsafe 'operator[]'%select{, use safe "
158+
"function '%1() instead|}0")
159+
<< FixFunctionEmptyArgs.empty() << FixFunctionEmptyArgs.str()
160+
<< MatchedExpr->getCallee()->getSourceRange();
161+
if (!FixFunctionEmptyArgs.empty()) {
162+
D << FixItHint::CreateInsertion(OCE->getArg(0)->getBeginLoc(),
163+
FixFunctionEmptyArgs.str() + "(")
164+
<< FixItHint::CreateRemoval(LeftBracket)
165+
<< FixItHint::CreateReplacement(RightBracket, ")");
166+
}
167+
} else {
168+
diag(MatchedExpr->getCallee()->getBeginLoc(),
169+
"possibly unsafe 'operator[]', use safe function '%0()' instead")
170+
<< FixFunction.str() << MatchedExpr->getCallee()->getSourceRange()
171+
<< FixItHint::CreateInsertion(OCE->getArg(0)->getBeginLoc(),
172+
FixFunction.str() + "(")
173+
<< FixItHint::CreateReplacement(LeftBracket, ", ")
174+
<< FixItHint::CreateReplacement(RightBracket, ")");
175+
}
176+
}
177+
} else if (const auto *MCE = dyn_cast<CXXMemberCallExpr>(MatchedExpr)) {
178+
// Case: a.operator[](i) or a->operator[](i)
179+
const auto *Callee = dyn_cast<MemberExpr>(MCE->getCallee());
180+
181+
if (FixMode == At) {
182+
// Cases: a.operator[](i) => a.at(i) and a->operator[](i) => a->at(i)
183+
184+
const auto *MatchedOperator =
185+
Result.Nodes.getNodeAs<CXXMethodDecl>("operator");
186+
187+
const CXXMethodDecl *Alternative = findAlternativeAt(MatchedOperator);
188+
if (!Alternative) {
189+
diag(Callee->getBeginLoc(), "possibly unsafe 'operator[]', consider "
190+
"bounds-safe alternative 'at()'")
191+
<< Callee->getSourceRange();
192+
return;
193+
}
194+
diag(MatchedExpr->getCallee()->getBeginLoc(),
195+
"possibly unsafe 'operator[]', consider "
196+
"bounds-safe alternative 'at()'")
197+
<< FixItHint::CreateReplacement(
198+
SourceRange(Callee->getMemberLoc(), Callee->getEndLoc()),
199+
"at");
200+
201+
diag(Alternative->getBeginLoc(), "viable 'at()' defined here",
202+
DiagnosticIDs::Note)
203+
<< Alternative->getNameInfo().getSourceRange();
204+
205+
} else if (FixMode == Function) {
206+
// Cases: a.operator[](i) => f(a, i) and a->operator[](i) => f(*a, i)
207+
const auto *Callee = dyn_cast<MemberExpr>(MCE->getCallee());
208+
209+
const bool EmptySubscript =
210+
MCE->getMethodDecl()->getNumNonObjectParams() == 0;
211+
212+
std::string BeginInsertion =
213+
(EmptySubscript ? FixFunctionEmptyArgs.str() : FixFunction.str()) +
214+
"(";
215+
216+
if (Callee->isArrow())
217+
BeginInsertion += "*";
218+
219+
// Since C++23, the subscript operator may also be called without an
220+
// argument, which makes the following distinction necessary
221+
if (EmptySubscript) {
222+
auto D = diag(MatchedExpr->getCallee()->getBeginLoc(),
223+
"possibly unsafe 'operator[]'%select{, use safe "
224+
"function '%1()' instead|}0")
225+
<< FixFunctionEmptyArgs.empty() << FixFunctionEmptyArgs.str()
226+
<< Callee->getSourceRange();
227+
228+
if (!FixFunctionEmptyArgs.empty()) {
229+
D << FixItHint::CreateInsertion(MatchedExpr->getBeginLoc(),
230+
BeginInsertion)
231+
<< FixItHint::CreateRemoval(
232+
SourceRange(Callee->getOperatorLoc(),
233+
MCE->getRParenLoc().getLocWithOffset(-1)));
234+
}
235+
} else {
236+
diag(Callee->getBeginLoc(),
237+
"possibly unsafe 'operator[]', use safe function '%0()' instead")
238+
<< FixFunction.str() << Callee->getSourceRange()
239+
<< FixItHint::CreateInsertion(MatchedExpr->getBeginLoc(),
240+
BeginInsertion)
241+
<< FixItHint::CreateReplacement(
242+
SourceRange(
243+
Callee->getOperatorLoc(),
244+
MCE->getArg(0)->getBeginLoc().getLocWithOffset(-1)),
245+
", ");
246+
}
247+
}
248+
}
249+
}
250+
251+
} // namespace clang::tidy::cppcoreguidelines
252+
253+
namespace clang::tidy {
254+
using P = cppcoreguidelines::ProBoundsAvoidUncheckedContainerAccess;
255+
256+
llvm::ArrayRef<std::pair<P::FixModes, StringRef>>
257+
OptionEnumMapping<P::FixModes>::getEnumMapping() {
258+
static constexpr std::pair<P::FixModes, StringRef> Mapping[] = {
259+
{P::None, "none"}, {P::At, "at"}, {P::Function, "function"}};
260+
return {Mapping};
261+
}
262+
} // namespace clang::tidy
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
//===--- ProBoundsAvoidUncheckedContainerAccess.h - clang-tidy --*- C++ -*-===//
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+
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_BOUNDS_AVOID_UNCHECKED_CONTAINER_ACCESS_H
10+
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_BOUNDS_AVOID_UNCHECKED_CONTAINER_ACCESS_H
11+
12+
#include "../ClangTidyCheck.h"
13+
14+
namespace clang::tidy::cppcoreguidelines {
15+
16+
/// Flags calls to operator[] in STL containers and suggests replacing it with
17+
/// safe alternatives.
18+
///
19+
/// See
20+
/// https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#slcon3-avoid-bounds-errors
21+
/// For the user-facing documentation see:
22+
/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines/pro-bounds-avoid-unchecked-container-access.html
23+
class ProBoundsAvoidUncheckedContainerAccess : public ClangTidyCheck {
24+
public:
25+
ProBoundsAvoidUncheckedContainerAccess(StringRef Name,
26+
ClangTidyContext *Context);
27+
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
28+
return LangOpts.CPlusPlus;
29+
}
30+
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
31+
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
32+
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
33+
34+
enum FixModes { None, At, Function };
35+
36+
private:
37+
// A list of class names that are excluded from the warning
38+
std::vector<llvm::StringRef> ExcludedClasses;
39+
// Setting which fix to suggest
40+
FixModes FixMode;
41+
llvm::StringRef FixFunction;
42+
llvm::StringRef FixFunctionEmptyArgs;
43+
};
44+
} // namespace clang::tidy::cppcoreguidelines
45+
46+
namespace clang::tidy {
47+
template <>
48+
struct OptionEnumMapping<
49+
cppcoreguidelines::ProBoundsAvoidUncheckedContainerAccess::FixModes> {
50+
static ArrayRef<std::pair<
51+
cppcoreguidelines::ProBoundsAvoidUncheckedContainerAccess::FixModes,
52+
StringRef>>
53+
getEnumMapping();
54+
};
55+
} // namespace clang::tidy
56+
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_BOUNDS_AVOID_UNCHECKED_CONTAINER_ACCESS_H

clang-tools-extra/clang-tidy/modernize/TypeTraitsCheck.cpp

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -189,15 +189,6 @@ AST_MATCHER(TypeLoc, isType) {
189189
static constexpr char Bind[] = "";
190190

191191
void TypeTraitsCheck::registerMatchers(MatchFinder *Finder) {
192-
const ast_matchers::internal::VariadicDynCastAllOfMatcher<
193-
Stmt,
194-
DependentScopeDeclRefExpr>
195-
dependentScopeDeclRefExpr; // NOLINT(readability-identifier-naming)
196-
const ast_matchers::internal::VariadicDynCastAllOfMatcher<
197-
TypeLoc,
198-
DependentNameTypeLoc>
199-
dependentNameTypeLoc; // NOLINT(readability-identifier-naming)
200-
201192
// Only register matchers for trait<...>::value in c++17 mode.
202193
if (getLangOpts().CPlusPlus17) {
203194
Finder->addMatcher(mapAnyOf(declRefExpr, dependentScopeDeclRefExpr)

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,13 @@ New checks
135135
Detects default initialization (to 0) of variables with ``enum`` type where
136136
the enum has no enumerator with value of 0.
137137

138+
- New :doc:`cppcoreguidelines-pro-bounds-avoid-unchecked-container-access
139+
<clang-tidy/checks/cppcoreguidelines/pro-bounds-avoid-unchecked-container-access>`
140+
check.
141+
142+
Finds calls to ``operator[]`` in STL containers and suggests replacing them
143+
with safe alternatives.
144+
138145
- New :doc:`llvm-mlir-op-builder
139146
<clang-tidy/checks/llvm/use-new-mlir-op-builder>` check.
140147

0 commit comments

Comments
 (0)