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
107 const auto *MatchedExpr = Result.Nodes.getNodeAs<CallExpr>("caller");
108
109 if (FixMode == None) {
110 diag(MatchedExpr->getCallee()->getBeginLoc(),
111 "possibly unsafe 'operator[]', consider bounds-safe alternatives")
112 << MatchedExpr->getCallee()->getSourceRange();
113 return;
114 }
115
116 if (const auto *OCE = dyn_cast<CXXOperatorCallExpr>(MatchedExpr)) {
117 // Case: a[i]
118 const auto LeftBracket = SourceRange(OCE->getCallee()->getBeginLoc(),
119 OCE->getCallee()->getBeginLoc());
120 const auto RightBracket =
121 SourceRange(OCE->getOperatorLoc(), OCE->getOperatorLoc());
122
123 if (FixMode == At) {
124 // Case: a[i] => a.at(i)
125 const auto *MatchedOperator =
126 Result.Nodes.getNodeAs<CXXMethodDecl>("operator");
127 const CXXMethodDecl *Alternative = findAlternativeAt(MatchedOperator);
128
129 if (!Alternative) {
130 diag(MatchedExpr->getCallee()->getBeginLoc(),
131 "possibly unsafe 'operator[]', consider "
132 "bounds-safe alternatives")
133 << MatchedExpr->getCallee()->getSourceRange();
134 return;
135 }
136
137 diag(MatchedExpr->getCallee()->getBeginLoc(),
138 "possibly unsafe 'operator[]', consider "
139 "bounds-safe alternative 'at()'")
140 << MatchedExpr->getCallee()->getSourceRange()
141 << FixItHint::CreateReplacement(LeftBracket, ".at(")
142 << FixItHint::CreateReplacement(RightBracket, ")");
143
144 diag(Alternative->getBeginLoc(), "viable 'at()' is defined here",
145 DiagnosticIDs::Note)
146 << Alternative->getNameInfo().getSourceRange();
147
148 } else if (FixMode == Function) {
149 // Case: a[i] => f(a, i)
150 //
151 // Since C++23, the subscript operator may also be called without an
152 // argument, which makes the following distinction necessary
153 const bool EmptySubscript =
154 MatchedExpr->getDirectCallee()->getNumParams() == 0;
155
156 if (EmptySubscript) {
157 auto D = diag(MatchedExpr->getCallee()->getBeginLoc(),
158 "possibly unsafe 'operator[]'%select{, use safe "
159 "function '%1() instead|}0")
160 << FixFunctionEmptyArgs.empty() << 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 = dyn_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 auto D = diag(MatchedExpr->getCallee()->getBeginLoc(),
224 "possibly unsafe 'operator[]'%select{, use safe "
225 "function '%1()' instead|}0")
226 << FixFunctionEmptyArgs.empty() << FixFunctionEmptyArgs.str()
227 << Callee->getSourceRange();
228
229 if (!FixFunctionEmptyArgs.empty()) {
230 D << FixItHint::CreateInsertion(MatchedExpr->getBeginLoc(),
231 BeginInsertion)
232 << FixItHint::CreateRemoval(
233 SourceRange(Callee->getOperatorLoc(),
234 MCE->getRParenLoc().getLocWithOffset(-1)));
235 }
236 } else {
237 diag(Callee->getBeginLoc(),
238 "possibly unsafe 'operator[]', use safe function '%0()' instead")
239 << FixFunction.str() << Callee->getSourceRange()
240 << FixItHint::CreateInsertion(MatchedExpr->getBeginLoc(),
241 BeginInsertion)
242 << FixItHint::CreateReplacement(
243 SourceRange(
244 Callee->getOperatorLoc(),
245 MCE->getArg(0)->getBeginLoc().getLocWithOffset(-1)),
246 ", ");
247 }
248 }
249 }
250}
251
252} // namespace clang::tidy::cppcoreguidelines
253
254namespace clang::tidy {
256
257llvm::ArrayRef<std::pair<P::FixModes, StringRef>>
259 static constexpr std::pair<P::FixModes, StringRef> Mapping[] = {
260 {P::None, "none"}, {P::At, "at"}, {P::Function, "function"}};
261 return {Mapping};
262}
263} // 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