clang-tools 24.0.0git
ProBoundsAvoidUncheckedContainerAccessCheck.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 "../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 StringRef DefaultExclusionStr =
20 "::std::map;::std::unordered_map;::std::flat_map";
21
24 ClangTidyContext *Context)
25 : ClangTidyCheck(Name, Context),
26 ExcludedClasses(utils::options::parseStringList(
27 Options.get("ExcludeClasses", DefaultExclusionStr))),
28 FixMode(Options.get("FixMode", None)),
29 FixFunction(Options.get("FixFunction", "gsl::at")),
30 FixFunctionEmptyArgs(Options.get("FixFunctionEmptyArgs", FixFunction)) {}
31
34 Options.store(Opts, "ExcludeClasses",
36 Options.store(Opts, "FixMode", FixMode);
37 Options.store(Opts, "FixFunction", FixFunction);
38 Options.store(Opts, "FixFunctionEmptyArgs", FixFunctionEmptyArgs);
39}
40
41// TODO: if at() is defined in another class in the class hierarchy of the class
42// that defines the operator[] we matched on, findAlternative() will not detect
43// it.
44static const CXXMethodDecl *
45findAlternativeAt(const CXXMethodDecl *MatchedOperator) {
46 const CXXRecordDecl *Parent = MatchedOperator->getParent();
47 const QualType SubscriptThisObjType =
48 MatchedOperator->getFunctionObjectParameterReferenceType();
49
50 for (const CXXMethodDecl *Method : Parent->methods()) {
51 // Require 'Method' to be as accessible as 'MatchedOperator' or more
52 if (MatchedOperator->getAccess() < Method->getAccess())
53 continue;
54
55 if (MatchedOperator->isConst() != Method->isConst())
56 continue;
57
58 const QualType AtThisObjType =
59 Method->getFunctionObjectParameterReferenceType();
60 if (SubscriptThisObjType != AtThisObjType)
61 continue;
62
63 if (!Method->getNameInfo().getName().isIdentifier() ||
64 Method->getName() != "at")
65 continue;
66
67 const bool SameReturnType =
68 Method->getReturnType() == MatchedOperator->getReturnType();
69 if (!SameReturnType)
70 continue;
71
72 const bool SameNumberOfArguments =
73 Method->getNumParams() == MatchedOperator->getNumParams();
74 if (!SameNumberOfArguments)
75 continue;
76
77 for (unsigned ArgInd = 0; ArgInd < Method->getNumParams(); ArgInd++) {
78 const bool SameArgType =
79 Method->parameters()[ArgInd]->getOriginalType() ==
80 MatchedOperator->parameters()[ArgInd]->getOriginalType();
81 if (!SameArgType)
82 continue;
83 }
84
85 return Method;
86 }
87 return nullptr;
88}
89
91 MatchFinder *Finder) {
92 Finder->addMatcher(
93 mapAnyOf(cxxOperatorCallExpr, cxxMemberCallExpr)
94 .with(callee(
95 cxxMethodDecl(
96 hasOverloadedOperatorName("[]"),
97 anyOf(parameterCountIs(0), parameterCountIs(1)),
98 unless(matchers::matchesAnyListedRegexName(ExcludedClasses)))
99 .bind("operator")))
100 .bind("caller"),
101 this);
102}
103
105 const MatchFinder::MatchResult &Result) {
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 const auto D = diag(MatchedExpr->getCallee()->getBeginLoc(),
157 "possibly unsafe 'operator[]'%select{, use safe "
158 "function '%1() instead|}0")
159 << FixFunctionEmptyArgs.empty()
160 << FixFunctionEmptyArgs.str()
161 << MatchedExpr->getCallee()->getSourceRange();
162 if (!FixFunctionEmptyArgs.empty()) {
163 D << FixItHint::CreateInsertion(OCE->getArg(0)->getBeginLoc(),
164 FixFunctionEmptyArgs.str() + "(")
165 << FixItHint::CreateRemoval(LeftBracket)
166 << FixItHint::CreateReplacement(RightBracket, ")");
167 }
168 } else {
169 diag(MatchedExpr->getCallee()->getBeginLoc(),
170 "possibly unsafe 'operator[]', use safe function '%0()' instead")
171 << FixFunction.str() << MatchedExpr->getCallee()->getSourceRange()
172 << FixItHint::CreateInsertion(OCE->getArg(0)->getBeginLoc(),
173 FixFunction.str() + "(")
174 << FixItHint::CreateReplacement(LeftBracket, ", ")
175 << FixItHint::CreateReplacement(RightBracket, ")");
176 }
177 }
178 } else if (const auto *MCE = dyn_cast<CXXMemberCallExpr>(MatchedExpr)) {
179 // Case: a.operator[](i) or a->operator[](i)
180 const auto *Callee = cast<MemberExpr>(MCE->getCallee());
181
182 if (FixMode == At) {
183 // Cases: a.operator[](i) => a.at(i) and a->operator[](i) => a->at(i)
184
185 const auto *MatchedOperator =
186 Result.Nodes.getNodeAs<CXXMethodDecl>("operator");
187
188 const CXXMethodDecl *Alternative = findAlternativeAt(MatchedOperator);
189 if (!Alternative) {
190 diag(Callee->getBeginLoc(), "possibly unsafe 'operator[]', consider "
191 "bounds-safe alternative 'at()'")
192 << Callee->getSourceRange();
193 return;
194 }
195 diag(MatchedExpr->getCallee()->getBeginLoc(),
196 "possibly unsafe 'operator[]', consider "
197 "bounds-safe alternative 'at()'")
198 << FixItHint::CreateReplacement(
199 SourceRange(Callee->getMemberLoc(), Callee->getEndLoc()),
200 "at");
201
202 diag(Alternative->getBeginLoc(), "viable 'at()' defined here",
203 DiagnosticIDs::Note)
204 << Alternative->getNameInfo().getSourceRange();
205
206 } else if (FixMode == Function) {
207 // Cases: a.operator[](i) => f(a, i) and a->operator[](i) => f(*a, i)
208 const auto *Callee = dyn_cast<MemberExpr>(MCE->getCallee());
209
210 const bool EmptySubscript =
211 MCE->getMethodDecl()->getNumNonObjectParams() == 0;
212
213 std::string BeginInsertion =
214 (EmptySubscript ? FixFunctionEmptyArgs.str() : FixFunction.str()) +
215 "(";
216
217 if (Callee->isArrow())
218 BeginInsertion += '*';
219
220 // Since C++23, the subscript operator may also be called without an
221 // argument, which makes the following distinction necessary
222 if (EmptySubscript) {
223 const auto D = diag(MatchedExpr->getCallee()->getBeginLoc(),
224 "possibly unsafe 'operator[]'%select{, use safe "
225 "function '%1()' instead|}0")
226 << FixFunctionEmptyArgs.empty()
227 << FixFunctionEmptyArgs.str()
228 << Callee->getSourceRange();
229
230 if (!FixFunctionEmptyArgs.empty()) {
231 D << FixItHint::CreateInsertion(MatchedExpr->getBeginLoc(),
232 BeginInsertion)
233 << FixItHint::CreateRemoval(
234 SourceRange(Callee->getOperatorLoc(),
235 MCE->getRParenLoc().getLocWithOffset(-1)));
236 }
237 } else {
238 diag(Callee->getBeginLoc(),
239 "possibly unsafe 'operator[]', use safe function '%0()' instead")
240 << FixFunction.str() << Callee->getSourceRange()
241 << FixItHint::CreateInsertion(MatchedExpr->getBeginLoc(),
242 BeginInsertion)
243 << FixItHint::CreateReplacement(
244 SourceRange(
245 Callee->getOperatorLoc(),
246 MCE->getArg(0)->getBeginLoc().getLocWithOffset(-1)),
247 ", ");
248 }
249 }
250 }
251}
252
253} // namespace clang::tidy::cppcoreguidelines
254
255namespace clang::tidy {
257
258llvm::ArrayRef<std::pair<P::FixModes, StringRef>>
260 static constexpr std::pair<P::FixModes, StringRef> Mapping[] = {
261 {P::None, "none"}, {P::At, "at"}, {P::Function, "function"}};
262 return {Mapping};
263}
264} // 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.
static const CXXMethodDecl * findAlternativeAt(const CXXMethodDecl *MatchedOperator)
inline ::clang::ast_matchers::internal::Matcher< NamedDecl > matchesAnyListedRegexName(llvm::ArrayRef< StringRef > NameList)
std::string serializeStringList(ArrayRef< StringRef > Strings)
Serialize a sequence of names that can be parsed by parseStringList.
cppcoreguidelines::ProBoundsAvoidUncheckedContainerAccessCheck P
llvm::StringMap< ClangTidyValue > OptionMap
static ArrayRef< std::pair< T, StringRef > > getEnumMapping()=delete