clang-tools 22.0.0git
ProBoundsAvoidUncheckedContainerAccess.cpp
Go to the documentation of this file.
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
10#include "../utils/Matchers.h"
12#include "clang/ASTMatchers/ASTMatchFinder.h"
13#include "llvm/ADT/StringRef.h"
14
15using namespace clang::ast_matchers;
16
18
19static constexpr llvm::StringRef DefaultExclusionStr =
20 "::std::map;::std::unordered_map;::std::flat_map";
21
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
33 Options.store(Opts, "ExcludeClasses",
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.
43static const CXXMethodDecl *
44findAlternativeAt(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
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
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
253namespace clang::tidy {
255
256llvm::ArrayRef<std::pair<P::FixModes, StringRef>>
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
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
Flags calls to operator[] in STL containers and suggests replacing it with safe alternatives.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
static const CXXMethodDecl * findAlternativeAt(const CXXMethodDecl *MatchedOperator)
inline ::clang::ast_matchers::internal::Matcher< NamedDecl > matchesAnyListedName(llvm::ArrayRef< StringRef > NameList)
std::string serializeStringList(ArrayRef< StringRef > Strings)
Serialize a sequence of names that can be parsed by parseStringList.
cppcoreguidelines::ProBoundsAvoidUncheckedContainerAccess P
llvm::StringMap< ClangTidyValue > OptionMap
static ArrayRef< std::pair< T, StringRef > > getEnumMapping()=delete