clang-tools 22.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 llvm::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::matchesAnyListedName(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 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 = 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.
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::ProBoundsAvoidUncheckedContainerAccessCheck P
llvm::StringMap< ClangTidyValue > OptionMap
static ArrayRef< std::pair< T, StringRef > > getEnumMapping()=delete